Перейти к основному содержимому

5.15. Справочник по Lua

Разработчику Архитектору

Справочник по Lua

Основы языка

Версия языка

Актуальная стабильная версия — Lua 5.4. Она включает поддержку константных локальных переменных, улучшенное управление памятью, новые варианты поведения для __close и другие уточнения по сравнению с Lua 5.3 и 5.2.


Типы данных

Lua имеет восемь базовых типов данных:

  1. nil — значение, обозначающее отсутствие значения. Единственное возможное значение этого типа — nil.
  2. boolean — логический тип. Возможные значения: true, false.
  3. number — числовой тип. По умолчанию представляет собой число с плавающей точкой двойной точности (double). В некоторых сборках может быть целочисленным (long long) или комбинированным.
  4. integer — подтип числового типа, представляющий целые числа. Доступен как отдельный внутренний тип, но внешне проявляется как number.
  5. string — неизменяемая последовательность байтов. Поддерживает UTF-8, но не проверяет корректность кодировки. Может содержать нулевые байты.
  6. function — исполняемый блок кода. Может быть обычной функцией, анонимной функцией, замыканием или C-функцией.
  7. table — основная структура данных. Реализует ассоциативный массив, список, множество, объект и другие абстракции.
  8. userdata — блок памяти, управляемый из C. Используется при встраивании Lua в приложения.
  9. thread — легковесный поток выполнения (корутина). Не связан с системными потоками.

Примечание: Lua 5.4 официально перечисляет восемь типов, где integer и float считаются подтипами number.

Функция type(x) возвращает строковое представление типа значения x.


Переменные

Объявление и присваивание

Переменные в Lua не требуют объявления типа. Присваивание создаёт переменную в текущей области видимости.

x = 10
name = "Alice"

Локальные переменные

Локальные переменные объявляются с помощью ключевого слова local. Они существуют только в пределах блока, в котором объявлены.

local y = 20
local message = "Hello"

В Lua 5.4 можно объявить локальную переменную как константу с помощью <const>:

local <const> PI = 3.14159

Попытка изменить такую переменную вызывает ошибку компиляции.

Глобальные переменные

Любая переменная без local становится глобальной. Глобальные переменные хранятся в таблице _G.

global_var = "visible everywhere"
print(_G.global_var) -- то же самое

Множественное присваивание

Lua поддерживает одновременное присваивание нескольких значений:

a, b = 1, 2
x, y = y, x -- обмен значениями

Если значений больше, чем переменных — лишние игнорируются. Если переменных больше — недостающим присваивается nil.


Операторы

Арифметические операторы

  • + — сложение
  • - — вычитание
  • * — умножение
  • / — деление (всегда возвращает число с плавающей точкой)
  • // — целочисленное деление (округление к минус бесконечности)
  • % — остаток от деления (a % b == a - (a // b) * b)
  • ^ — возведение в степень
  • - (унарный) — смена знака

Побитовые операторы (Lua 5.3+)

  • & — побитовое И
  • | — побитовое ИЛИ
  • ~ — побитовое исключающее ИЛИ
  • << — сдвиг влево
  • >> — сдвиг вправо
  • ~ (унарный) — побитовое НЕ (дополнение до двух)

Операторы сравнения

  • == — равенство
  • ~= — неравенство
  • <, >, <=, >= — числовые и строковые сравнения

Сравнение таблиц, функций, userdata и корутин выполняется по ссылке.

Логические операторы

  • and — логическое И
  • or — логическое ИЛИ
  • not — логическое НЕ

Эти операторы используют ленивую семантику и возвращают один из своих операндов, а не обязательно true/false.

Пример:

x = false or "default"  --> x = "default"
y = 42 and "yes" --> y = "yes"

Конкатенация

  • .. — конкатенация строк и чисел. Числа автоматически преобразуются в строки.
greeting = "Hello, " .. "world!"  --> "Hello, world!"
message = "Value: " .. 42 --> "Value: 42"

Приоритет операторов (от высшего к низшему)

  1. ^
  2. унарные -, #, not, ~
  3. *, /, //, %
  4. +, -
  5. ..
  6. побитовые <<, >>, &, ~, |
  7. <, >, <=, >=, ~=, ==
  8. and
  9. or

Операторы одного уровня ассоциативны слева направо, кроме ^ и .., которые ассоциативны справа налево.


Управляющие конструкции

Условные операторы

if–then–elseif–else

if condition1 then
-- блок 1
elseif condition2 then
-- блок 2
else
-- блок по умолчанию
end

Условия должны быть выражениями. Только nil и false считаются ложными.

Циклы

while

while condition do
-- тело цикла
end

repeat–until

repeat
-- тело цикла
until condition

Тело цикла выполняется хотя бы один раз. Условие проверяется после каждой итерации.

for (числовой)

for i = start, limit, step do
-- тело
end
  • start, limit, step — числовые выражения.
  • step по умолчанию равен 1.
  • Переменная цикла i — локальная и доступна только внутри цикла.
  • Изменение i внутри цикла не влияет на количество итераций.

for (обобщённый)

for key, value in iterator, state, control do
-- тело
end

Часто используется с pairs() и ipairs():

for k, v in pairs(table) do ... end
for i, v in ipairs(array) do ... end

Строки

Строки задаются в одинарных '...', двойных "..." или длинных скобках [[...]].

Длинные строки сохраняют пробелы и переносы. Можно использовать уровни вложенности: [=[ ... ]=], [==[ ... ]==] и так далее.

Экранирование:

  • \n — новая строка
  • \t — табуляция
  • \\ — обратный слеш
  • \", \' — кавычки
  • \ddd — байт в десятичной системе (до трёх цифр)

Строки неизменяемы. Любая операция над строкой создаёт новую строку.


Комментарии

  • Однострочные: -- это комментарий
  • Многострочные: --[[ это многострочный комментарий ]]

Многострочные комментарии могут быть отключены, если добавить символ - перед открывающей скобкой:

---[[ отключённый комментарий ]]
print("этот код выполняется")

Значения истинности

В Lua только два значения считаются ложными: nil и false.
Все остальные значения, включая 0, пустую строку "", пустую таблицу {}, считаются истинными.


Оператор # (длина)

Применяется к строкам и «массивоподобным» таблицам.

  • Для строки — возвращает количество байтов.
  • Для таблицы — возвращает длину последовательности, то есть наибольший целочисленный ключ n, такой что t[1], t[2], ..., t[n] не равны nil, а t[n+1] равно nil.

Поведение неопределено, если таблица содержит «дыры» (пропущенные индексы).


Функции, замыкания, вызовы

Функции

Функция в Lua — это значение первого класса. Её можно присваивать переменным, передавать как аргумент, возвращать из других функций.

Объявление функции

Именованная функция (глобальная):

function greet(name)
return "Hello, " .. name
end

Это сокращение для:

greet = function(name)
return "Hello, " .. name
end

Локальная функция:

local function add(a, b)
return a + b
end

Это сокращение для:

local add
add = function(a, b)
return a + b
end

Такая форма позволяет рекурсивные вызовы внутри локальной функции.

Параметры и аргументы

Lua не проверяет количество переданных аргументов.

  • Лишние аргументы игнорируются.
  • Недостающим параметрам присваивается nil.

Пример:

function f(a, b, c) print(a, b, c) end
f(1, 2) --> 1 2 nil
f(1, 2, 3, 4) --> 1 2 3

Вариадические функции (с переменным числом аргументов)

Обозначаются через ...:

function sum(...)
local args = {...}
local total = 0
for i = 1, #args do
total = total + args[i]
end
return total
end

Внутри функции ... представляет собой список значений. Его можно использовать напрямую:

function echo(...)
return ...
end

Оператор select помогает работать с ...:

  • select(n, ...) — возвращает все аргументы, начиная с n-го.
  • select("#", ...) — возвращает количество аргументов (включая nil).

Пример:

function first_arg(...)
return select(1, ...)
end

Возврат нескольких значений

Функция может возвращать несколько значений:

function coords()
return 10, 20
end

x, y = coords() -- x = 10, y = 20

Если вызов функции не находится в «расширяющем контексте» (например, не в списке присваивания или аргументов), возвращается только первое значение.

print(coords())      --> 10 20
local a = coords() --> a = 10

Расширяющий контекст включает:

  • Множественное присваивание
  • Список аргументов при вызове другой функции
  • Конструктор таблицы { f() } — в этом случае все возвращённые значения вставляются

Исключение: последний элемент в списке инициализации таблицы не расширяется, если за ним следует запятая или закрывающая скобка без дополнительных элементов.

Хвостовая рекурсия (tail call)

Вызов функции в хвостовой позиции (последнее действие функции) оптимизируется: он не потребляет дополнительный стек.

function factorial(n, acc)
acc = acc or 1
if n <= 1 then
return acc
else
return factorial(n - 1, n * acc) -- хвостовой вызов
end
end

Такой вызов эквивалентен циклу и не вызывает переполнения стека даже при больших n.

Хвостовой вызов происходит только при точном синтаксисе:

  • return func(args)
  • без дополнительных операций после вызова

Недопустимо:

return func() + 1     -- не хвостовой
return g(func()) -- не хвостовой

Замыкания

Замыкание — это функция, захватывающая переменные из внешней области видимости.

function make_counter()
local count = 0
return function()
count = count + 1
return count
end
end

c1 = make_counter()
c2 = make_counter()

print(c1()) --> 1
print(c1()) --> 2
print(c2()) --> 1

Каждый вызов make_counter создаёт новую локальную переменную count, и каждая возвращённая функция сохраняет ссылку на свою копию.

Замыкания позволяют инкапсуляцию состояния без использования таблиц или объектов.


Анонимные функции

Функции без имени часто используются как аргументы:

table.sort(names, function(a, b) return a < b end)

Функции как методы

Lua не имеет встроенной поддержки объектов, но методы эмулируются через синтаксис :

obj = {
value = 42,
get = function(self)
return self.value
end
}

-- или
function obj:set(v)
self.value = v
end

Вызов:

obj:get()      -- эквивалентно obj.get(obj)
obj:set(100) -- эквивалентно obj.set(obj, 100)

Синтаксис obj:method() автоматически передаёт obj как первый аргумент (self).


Стандартные функции высшего порядка

Lua не предоставляет встроенные функции map, filter, reduce, но их легко реализовать:

function map(t, fn)
local result = {}
for k, v in pairs(t) do
result[k] = fn(v)
end
return result
end

function filter(t, pred)
local result = {}
for k, v in pairs(t) do
if pred(v) then
table.insert(result, v)
end
end
return result
end

Обработка ошибок в функциях

Lua использует pcall и xpcall для безопасного вызова:

local success, result = pcall(some_function, arg1, arg2)
if success then
print("Успех:", result)
else
print("Ошибка:", result)
end

xpcall принимает обработчик ошибок:

xpcall(f, error_handler)

Функции и сборка мусора

Замыкания, анонимные функции и локальные переменные управляются сборщиком мусора. Циклические ссылки между таблицами и функциями разрешаются корректно благодаря инкрементальному сборщику мусора с поддержкой финализаторов (__gc).


Особенности производительности

  • Вызов функции имеет небольшие накладные расходы.
  • Локальные функции быстрее глобальных.
  • Использование local для часто вызываемых функций (local assert = assert) ускоряет доступ.
  • Хвостовые вызовы не увеличивают глубину стека.

Таблицы, итераторы, метаметоды

Таблицы

Таблица — единственная встроенная структура данных в Lua. Она объединяет свойства массива, словаря, множества, объекта и модуля.

Создание таблиц

Пустая таблица:

t = {}

Таблица с начальными значениями:

point = { x = 10, y = 20 }
colors = { "red", "green", "blue" }
mixed = { name = "Alice", 42, true }

В конструкторе:

  • Записи вида key = value создают хеш-часть.
  • Значения без ключа ("red", 42) добавляются в массивную часть с целочисленными ключами, начиная с 1.

Эквивалентные формы:

t = { x = 1 }
t = { ["x"] = 1 }
t = {}
t.x = 1
t["x"] = 1

Доступ к элементам

t.key      -- синтаксический сахар для t["key"]
t[key] -- динамический доступ
t[100] -- числовой ключ
t[nil] -- допустимо, но не рекомендуется (ключ nil игнорируется)

Ключом может быть любое значение, кроме nil и NaN. Числа и строки наиболее распространены.

Изменение и удаление

t.new_field = "value"
t[10] = "ten"
t.existing = nil -- удаление поля

Присваивание nil удаляет пару «ключ–значение» из таблицы.

Длина таблицы

Оператор #t работает только с последовательностями — таблицами, где целочисленные ключи идут подряд от 1 до n без пропусков.

a = {10, 20, 30}
print(#a) --> 3

b = {10, nil, 30}
print(#b) --> 1 или 3 (неопределённое поведение)

Для произвольных таблиц используйте явный подсчёт:

function table_size(t)
local count = 0
for _ in pairs(t) do count = count + 1 end
return count
end

Стандартные функции для работы с таблицами (table.*)

Модуль table предоставляет вспомогательные функции.

table.insert(list, [pos,] value)

Вставляет value в список list:

  • Без pos — в конец.
  • С pos — на указанную позицию (остальные сдвигаются).
t = {1, 2}
table.insert(t, 3) --> {1, 2, 3}
table.insert(t, 2, 99) --> {1, 99, 2, 3}

table.remove(list, [pos])

Удаляет и возвращает элемент по индексу pos (по умолчанию — последний).

x = table.remove(t)       --> удаляет последний
y = table.remove(t, 1) --> удаляет первый

table.concat(list, [sep], [i], [j])

Объединяет строковые представления элементов списка от i до j через разделитель sep.

t = {"a", "b", "c"}
s = table.concat(t, ", ") --> "a, b, c"

table.sort(list, [comp])

Сортирует список на месте. По умолчанию — по возрастанию.

table.sort(t, function(a, b) return a > b end)  -- по убыванию

Функция сравнения должна быть транзитивной и не иметь побочных эффектов.

table.move(a1, f, e, t, a2)

Перемещает элементы из таблицы a1 с индексов [f, e] в таблицу a2, начиная с индекса t. Возвращает a2.

dest = {}
table.move(src, 1, 5, 1, dest)

table.unpack(list, [i], [j])...

(В Lua 5.2–5.3 называлась unpack; в 5.4 перемещена в table.unpack.)

Возвращает элементы списка как отдельные значения.

x, y, z = table.unpack({10, 20, 30})  --> x=10, y=20, z=30

Итераторы

pairs(t)

Возвращает итератор по всем парам ключ–значение в таблице. Порядок обхода не определён.

for k, v in pairs(t) do
print(k, v)
end

ipairs(t)

Возвращает итератор по целочисленным ключам, начиная с 1, пока не встретится nil.

for i, v in ipairs(t) do
print(i, v)
end

Останавливается при первой «дыре».

Пользовательские итераторы

Итератор — это функция, возвращающая следующее значение при каждом вызове.

Пример: итератор по диапазону

function range(from, to)
local current = from - 1
return function()
current = current + 1
if current <= to then
return current
end
end
end

for i in range(1, 5) do print(i) end

Генератор итератора возвращает три значения: функцию, неизменяемое состояние и начальный контрольный элемент.


Метаметоды и метатаблицы

Метатаблица — таблица, определяющая поведение другой таблицы при определённых операциях.

Установка метатаблицы:

setmetatable(t, mt)

Получение метатаблицы:

mt = getmetatable(t)

Основные метаметоды

МетаметодОписание
__indexВызывается при чтении отсутствующего ключа. Может быть функцией или таблицей.
__newindexВызывается при записи в отсутствующий или новый ключ.
__addОператор +
__subОператор -
__mulОператор *
__divОператор /
__modОператор %
__powОператор ^
__unmУнарный минус -
__idivЦелочисленное деление //
__bandПобитовое И &
__borПобитовое ИЛИ `
__bxorПобитовое XOR ~
__bnotПобитовое НЕ ~ (унарный)
__shlСдвиг влево <<
__shrСдвиг вправо >>
__concatКонкатенация ..
__lenОператор #
__eqОператор ==
__ltОператор <
__leОператор <=
__callВызов таблицы как функции t()
__tostringПреобразование в строку (например, при print)
__gcФинализатор (только для userdata в Lua 5.3+, для таблиц — начиная с Lua 5.4)
__closeИспользуется в механизме toclose (Lua 5.4)

Пример: вектор с перегруженными операторами

Vector = {}
Vector.__index = Vector

function Vector.new(x, y)
return setmetatable({x = x or 0, y = y or 0}, Vector)
end

function Vector.__add(a, b)
return Vector.new(a.x + b.x, a.y + b.y)
end

function Vector.__tostring(v)
return "(" .. v.x .. ", " .. v.y .. ")"
end

v1 = Vector.new(1, 2)
v2 = Vector.new(3, 4)
v3 = v1 + v2
print(v3) --> (4, 6)

__index как таблица

Часто используется для наследования:

parent = { value = 42 }
child = setmetatable({}, { __index = parent })
print(child.value) --> 42

__newindex для контроля записи

protected = {}
mt = {
__newindex = function(t, k, v)
error("Запись запрещена в " .. tostring(k))
end
}
setmetatable(protected, mt)

Реализация объектов и классов

Lua не имеет встроенных классов, но их легко эмулировать.

Прототипный стиль

Account = { balance = 0 }
Account.__index = Account

function Account:new(initial_balance)
local obj = { balance = initial_balance or 0 }
setmetatable(obj, self)
return obj
end

function Account:deposit(amount)
self.balance = self.balance + amount
end

a = Account:new(100)
a:deposit(50)
print(a.balance) --> 150

Классический стиль с закрытыми полями (через замыкания)

function new_account(initial)
local self = { balance = initial }
return {
deposit = function(amount) self.balance = self.balance + amount end,
get_balance = function() return self.balance end
}
end

Модули, загрузка кода, стандартная библиотека

Модули и система загрузки

Lua поддерживает модульную организацию кода через функцию require.

Создание модуля

Модуль — это таблица, возвращаемая из файла.

-- math_utils.lua
local M = {}

function M.add(a, b)
return a + b
end

function M.multiply(a, b)
return a * b
end

return M

Использование модуля

local utils = require("math_utils")
print(utils.add(2, 3)) --> 5

Функция require:

  • Ищет модуль по имени (точки преобразуются в слеши: mylib.utilsmylib/utils.lua).
  • Кэширует результат в таблице package.loaded.
  • Не перезагружает модуль при повторном вызове.

Чтобы перезагрузить модуль, нужно очистить кэш:

package.loaded["math_utils"] = nil

Альтернативный стиль (устаревший, но встречающийся)

Ранее использовался глобальный подход:

-- старый стиль
local modname = ...
local M = {}
_G[modname] = M

Современный код использует return.

Пути поиска модулей

Пути задаются в:

  • package.path — для Lua-файлов (.lua)
  • package.cpath — для C-библиотек (.so, .dll)

Формат пути:

?;?.lua;/usr/local/share/lua/5.4/?

Где ? заменяется на имя модуля.

Пример:

package.path = package.path .. ";./modules/?.lua"

Загрузка и выполнение кода

Lua позволяет динамически загружать и выполнять строки кода.

load(string, [chunkname], [mode], [env])

Возвращает функцию или ошибку.

  • string — код как строка.
  • chunkname — имя чанка (для отладки).
  • mode"t" (текст), "b" (бинарный), "bt" (оба).
  • env — окружение (таблица, используемая как _ENV).

Пример:

local f, err = load("return x + 1", "adder", "t", { x = 10 })
if f then
print(f()) --> 11
end

loadfile(filename, [mode], [env])

Аналог load, но читает код из файла.

dofile(filename)

Выполняет файл немедленно:

dofile("config.lua")  -- эквивалентно loadfile + вызов

Не кэшируется и не использует require.


Окружение (_ENV)

Начиная с Lua 5.2, глобальные переменные разрешаются через неявную локальную переменную _ENV.

По умолчанию:

_ENV = _G

Можно переопределить окружение для блока:

local custom_env = { print = print, x = 42 }
local f = load("print(x)", "test", "t", custom_env)
f() --> 42

Или внутри файла:

-- restricted.lua
local _ENV = { print = print }
x = 10 -- запись в локальное окружение
print(x) -- работает
print(y) -- ошибка, если y не в _ENV

Это позволяет создавать песочницы.


Стандартная библиотека

Lua поставляется с набором встроенных модулей.

string — работа со строками

Основные функции:

  • string.len(s) — длина строки (эквивалент #s)
  • string.sub(s, i, j) — подстрока с i по j (отрицательные индексы — с конца)
  • string.char(...) — создаёт строку из кодов символов
  • string.byte(s, [i], [j]) — возвращает коды символов
  • string.rep(s, n) — повторяет строку n раз
  • string.reverse(s) — переворачивает строку
  • string.upper(s), string.lower(s) — регистр
  • string.format(fmt, ...) — форматирование (аналог printf)

Шаблонные функции (pattern matching):

  • string.find(s, pattern, [init], [plain]) — поиск позиции
  • string.match(s, pattern, [init]) — извлечение по шаблону
  • string.gsub(s, pattern, repl, [n]) — замена
  • string.gmatch(s, pattern) — итератор по совпадениям

Шаблоны:

  • . — любой символ
  • %a — буква
  • %d — цифра
  • %w — буква или цифра
  • %s — пробельный символ
  • %p — знак препинания
  • %u — заглавная буква
  • %l — строчная буква
  • %x — шестнадцатеричная цифра
  • %z — нулевой байт

Квантификаторы:

  • + — один или более
  • * — ноль или более
  • - — ноль или более (нежадный)
  • ? — ноль или один

Группировка: ( ) — захватывающие скобки.

Пример:

date = "2026-01-17"
year, month, day = string.match(date, "(%d+)-(%d+)-(%d+)")

math — математические функции

  • math.pi, math.huge
  • math.abs, math.sqrt, math.sin, math.cos, math.tan, math.atan, math.atan2
  • math.exp, math.log, math.log10
  • math.floor, math.ceil, math.modf
  • math.min, math.max
  • math.random([m], [n]) — генератор случайных чисел
  • math.randomseed(x) — установка зерна

В Lua 5.3+ добавлены целочисленные функции:

  • math.type(x)"integer" или "float"
  • math.tointeger(x) — преобразование в целое
  • math.ult(m, n) — беззнаковое сравнение

io — ввод-вывод

  • io.input([file]) — установка входного потока
  • io.output([file]) — установка выходного потока
  • io.read(...) — чтение из текущего входного потока
  • io.write(...) — запись в текущий выходной поток
  • io.open(filename, mode) — открытие файла
  • io.close(file) — закрытие
  • io.lines([filename]) — итератор по строкам файла

Режимы открытия:

  • "r" — чтение
  • "w" — запись (очистка)
  • "a" — добавление
  • "r+" — чтение и запись
  • "w+" — чтение и запись (очистка)

os — системные операции

  • os.date([format], [time]) — форматирование даты
  • os.time([table]) — временная метка
  • os.difftime(t2, t1) — разница во времени
  • os.execute(command) — выполнение команды ОС
  • os.exit([code]) — завершение программы
  • os.getenv(varname) — получение переменной окружения
  • os.remove(filename) — удаление файла
  • os.rename(old, new) — переименование

coroutine — корутины (сопрограммы)

  • coroutine.create(f) — создаёт корутину
  • coroutine.resume(co, ...) — запускает или возобновляет
  • coroutine.yield(...) — приостанавливает и возвращает значения
  • coroutine.status(co)"running", "suspended", "dead", "normal"
  • coroutine.isyieldable() — можно ли вызвать yield в текущем контексте

Пример:

co = coroutine.create(function()
for i = 1, 3 do
coroutine.yield(i)
end
end)

print(coroutine.resume(co)) --> true, 1
print(coroutine.resume(co)) --> true, 2
print(coroutine.resume(co)) --> true, 3
print(coroutine.resume(co)) --> true

debug — отладка и интроспекция

Используется редко в production, но мощен для инструментов.

  • debug.getinfo(func, [what]) — информация о функции
  • debug.getlocal(thread, level, local) — имя и значение локальной переменной
  • debug.setlocal(...)
  • debug.getupvalue(func, up)
  • debug.setupvalue(func, up, value)
  • debug.traceback([thread], [message], [level]) — стек вызовов

utf8 (Lua 5.3+)

Работа с UTF-8 строками:

  • utf8.len(s) — количество Unicode-символов
  • utf8.char(...) — строка из кодовых точек
  • utf8.codepoint(s, [i], [j]) — итератор по кодовым точкам
  • utf8.offset(s, n, [i]) — позиция n-го символа

Особенности встраивания Lua

Lua часто используется как встраиваемый язык. В этом случае:

  • Хост-приложение управляет жизненным циклом lua_State.
  • C-функции регистрируются в Lua через lua_pushcfunction.
  • Таблицы и userdata передают данные между C и Lua.
  • Метаметоды __gc позволяют освобождать ресурсы.
  • Функция lua_pcall обеспечивает безопасный вызов с обработкой ошибок.

Пример регистрации:

static int l_add(lua_State *L) {
double a = lua_tonumber(L, 1);
double b = lua_tonumber(L, 2);
lua_pushnumber(L, a + b);
return 1;
}

// Регистрация
lua_register(L, "add", l_add);

Особенности Lua 5.4, управление памятью, стиль и производительность

Ключевые нововведения в Lua 5.4

Lua 5.4 (выпущена в 2020 году) внесла несколько важных улучшений по сравнению с 5.3 и 5.2.

1. Константные локальные переменные (<const>)

Можно объявить локальную переменную как константу:

local <const> MAX_SIZE = 1000

Попытка присвоить новое значение вызывает ошибку на этапе компиляции.

Такие переменные могут содержать только примитивные значения: nil, boolean, number, string.

2. Механизм toclose и метаметод __close

Позволяет автоматически освобождать ресурсы при выходе из блока.

local f <const> = io.open("data.txt", "r")
local data <toclose> = f
-- файл будет автоматически закрыт при выходе из блока

Работает для любого значения, чья метатаблица содержит __close:

local mt = {
__close = function(obj)
print("Очистка:", obj.name)
end
}

local resource <toclose> = setmetatable({ name = "test" }, mt)

Это аналог defer в Go или деструкторов в RAII.

3. Улучшенный сборщик мусора

  • Новый режим инкрементальной сборки с адаптивной скоростью.
  • Поддержка поколений (generational mode) — экспериментальный режим, оптимизированный для короткоживущих объектов.
  • Возможность явного управления через collectgarbage():
    • "stop" — остановить
    • "restart" — возобновить
    • "collect" — полная сборка
    • "count" — размер памяти в КБ
    • "step" — один шаг инкрементальной сборки
    • "setpause", "setstepmul" — настройка поведения

4. Целочисленный тип как отдельная категория

Хотя внешне integer и float остаются подтипами number, внутри они различаются. Это улучшает производительность и точность.

Функция math.type(x) возвращает "integer" или "float".

5. Безопасное сравнение беззнаковых целых

Функция math.ult(m, n) сравнивает два целых числа как беззнаковые.

6. Улучшенная обработка ошибок

  • Больше контекста в трассировке стека.
  • Поддержка пользовательских обработчиков ошибок в xpcall.

Управление памятью

Lua использует автоматическое управление памятью на основе инкрементального сборщика мусора с возможностью поколений.

Жизненный цикл объекта

  1. Объект создаётся (таблица, строка, функция и т.д.).
  2. Пока на него есть ссылки — он жив.
  3. Когда все ссылки исчезают — он становится недостижимым.
  4. Сборщик мусора освобождает память.

Финализаторы (__gc)

Для userdata (и таблиц, начиная с Lua 5.4) можно задать метаметод __gc:

local mt = {
__gc = function(obj)
print("Освобождение ресурса:", obj.id)
end
}
local obj = setmetatable({ id = 123 }, mt)
obj = nil
collectgarbage() -- вызовет __gc

Важно: финализаторы не гарантируют немедленного вызова. Они вызываются во время сборки мусора.

Советы по снижению давления на GC

  • Избегать создания временных таблиц и строк в горячих циклах.
  • Переиспользовать объекты, где возможно.
  • Использовать table.clear(t) (Lua 5.4) вместо t = {}, если таблица большая.
  • Минимизировать замыкания в циклах.

Стиль кода и лучшие практики

Именование

  • Переменные и функции: snake_case
  • Константы: UPPER_SNAKE_CASE
  • Локальные переменные: всегда объявлять с local
  • Избегать глобальных переменных

Структура файла

  • Один модуль — один файл.
  • Возвращать таблицу в конце.
  • Не смешивать исполняемый код и определения функций.

Обработка ошибок

  • Использовать assert для внутренних проверок.
  • Использовать pcall/xpcall для внешних вызовов.
  • Возвращать nil, error_message в случае ошибки (идиома Lua).

Пример:

function divide(a, b)
if b == 0 then
return nil, "division by zero"
end
return a / b
end

Производительность

  • Доступ к локальным переменным быстрее, чем к глобальным.
  • Кэшировать часто используемые функции: local table_insert = table.insert
  • Избегать .. в циклах — использовать table.concat
  • Использовать ipairs для массивов, pairs — для хешей
  • Не вызывать #t в цикле — сохранить в переменную

Совместимость между версиями

ФичаLua 5.1Lua 5.2Lua 5.3Lua 5.4
_ENVнетдадада
gotoнетдадада
Целые числанетнетдада
bit32дадаудалён
utf8нетнетдада
<const>нетнетнетда
<toclose>нетнетнетда
__gc для таблицнетнетнетда
table.moveнетнетдада
table.unpackunpackunpacktable.unpacktable.unpacktable.unpack

Для максимальной переносимости:

  • Избегать версионно-специфичных фич.
  • Проверять LUA_VERSION_NUM при необходимости.
  • Использовать load вместо loadstring (устарело в 5.2).

Распространённые ловушки

  1. Глобальные переменные по ошибке

    local t = { x = 1 }
    t.x = 2 -- правильно
    t.x = 2 -- опечатка: t.x = 2 vs t.x = 2 — но если написать t.x = 2 как t.x = 2, всё ок
    -- опасность: mistyped = 10 -- глобальная!

    Решение: использовать строгий режим через метатаблицу _G.

  2. Изменение таблицы во время итерации pairs
    Добавление/удаление ключей во время pairs допустимо, но может привести к пропуску или повторному обходу элементов.

  3. Неправильное использование # для таблиц с дырами
    Всегда проверяйте, что таблица — сплошной массив.

  4. Замыкания в циклах

    funcs = {}
    for i = 1, 3 do
    funcs[i] = function() print(i) end
    end
    -- все функции выведут 4 (значение i после цикла)

    Решение: захватывать значение через параметр:

    funcs[i] = function() return function() print(i) end end ()
    -- или
    for i = 1, 3 do
    local i = i
    funcs[i] = function() print(i) end
    end